;**** A P P L I C A T I O N   N O T E   A V R 3 0 0 ************************
;*
;* Title		: I2C (Single) Master Implementation
;* Version		: 1.0 (BETA)
;* Last updated		: 97.08.27
;* Target		: AT90Sxxxx (any AVR device)
;*
;* Support email	: avr@atmel.com
;*
;* DESCRIPTION
;* 	Basic routines for communicating with I2C slave devices. This
;*	"single" master implementation is limited to one bus master on the
;*	I2C bus. Most applications do not need the multimaster ability
;*	the I2C bus provides. A single master implementation uses, by far,
;*	less resources and is less XTAL frequency dependent.
;*
;*	Some features :
;*	* All interrupts are free, and can be used for other activities.
;*	* Supports normal and fast mode.
;*	* Supports both 7-bit and 10-bit addressing.
;*	* Supports the entire AVR microcontroller family.
;*
;*	Main I2C functions :
;*	'i2c_start' -		Issues a start condition and sends address
;*				and transfer direction.
;*	'i2c_rep_start' -	Issues a repeated start condition and sends
;*				address and transfer direction.
;*	'i2c_do_transfer' -	Sends or receives data depending on
;*				direction given in address/dir byte.
;*	'i2c_stop' -		Terminates the data transfer by issue a
;*				stop condition.
;*
;* USAGE
;*	Transfer formats is described in the AVR300 documentation.
;*	(An example is shown in the 'main' code).	
;*
;* NOTES
;*	The I2C routines can be called either from non-interrupt or
;*	interrupt routines, not both.
;*
;* STATISTICS
;*	Code Size	: 81 words (maximum)
;*	Register Usage	: 4 High, 0 Low
;*	Interrupt Usage	: None
;*	Other Usage	: Uses two I/O pins on port D
;*	XTAL Range	: N/A
;*
;***************************************************************************

;**** Includes ****

.include "1200def.inc"			; change if an other device is used

;**** Global I2C Constants ****

.equ	SCLP	= 1			; SCL Pin number (port D)
.equ	SDAP	= 0			; SDA Pin number (port D)

.equ	b_dir	= 0			; transfer direction bit in i2cadr

.equ	i2crd	= 1
.equ	i2cwr	= 0

;**** Global Register Variables ****

.def	i2cdelay= r16			; Delay loop variable
.def	i2cdata	= r17			; I2C data transfer register
.def	i2cadr	= r18			; I2C address and direction register
.def	i2cstat	= r19			; I2C bus status register

;**** Interrupt Vectors ****

	rjmp	RESET			; Reset handle
;	( rjmp	EXT_INT0 )		; ( IRQ0 handle )
;	( rjmp	TIM0_OVF )		; ( Timer 0 overflow handle )
;	( rjmp	ANA_COMP )		; ( Analog comparator handle )


;***************************************************************************
;*
;* FUNCTION
;*	i2c_hp_delay
;*	i2c_qp_delay
;*
;* DESCRIPTION
;*	hp - half i2c clock period delay (normal: 5.0us / fast: 1.3us)
;*	qp - quarter i2c clock period delay (normal: 2.5us / fast: 0.6us)
;*
;*	SEE DOCUMENTATION !!!
;*
;* USAGE
;*	no parameters
;*
;* RETURN
;*	none
;*
;***************************************************************************

i2c_hp_delay:
	ldi	i2cdelay,2
i2c_hp_delay_loop:
	dec	i2cdelay
	brne	i2c_hp_delay_loop
	ret

i2c_qp_delay:
	ldi	i2cdelay,1	
i2c_qp_delay_loop:
	dec	i2cdelay
	brne	i2c_qp_delay_loop
	ret


;***************************************************************************
;*
;* FUNCTION
;*	i2c_rep_start
;*
;* DESCRIPTION
;*	Assert repeated start condition and sends slave address.
;*
;* USAGE
;*	i2cadr - Contains the slave address and transfer direction.
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to the address.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_start.
;*
;***************************************************************************

i2c_rep_start:
	sbi	DDRD,SCLP		; force SCL low
	cbi	DDRD,SDAP		; release SDA
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	i2c_qp_delay		; quarter period delay


;***************************************************************************
;*
;* FUNCTION
;*	i2c_start
;*
;* DESCRIPTION
;*	Generates start condition and sends slave address.
;*
;* USAGE
;*	i2cadr - Contains the slave address and transfer direction.
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to the address.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_write.
;*
;***************************************************************************

i2c_start:				
	mov	i2cdata,i2cadr		; copy address to transmitt register
	sbi	DDRD,SDAP		; force SDA low
	rcall	i2c_qp_delay		; quarter period delay


;***************************************************************************
;*
;* FUNCTION
;*	i2c_write
;*
;* DESCRIPTION
;*	Writes data (one byte) to the I2C bus. Also used for sending
;*	the address.
;*
;* USAGE
;*	i2cdata - Contains data to be transmitted.
;*
;* RETURN
;*	Carry flag - Set if the slave respond transfer.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_get_ack.
;*
;***************************************************************************

i2c_write:
	sec				; set carry flag
	rol	i2cdata			; shift in carry and out bit one
	rjmp	i2c_write_first
i2c_write_bit:
	lsl	i2cdata			; if transmit register empty
i2c_write_first:
	breq	i2c_get_ack		;	goto get acknowledge
	sbi	DDRD,SCLP		; force SCL low

	brcc	i2c_write_low		; if bit high
	nop				;	(equalize number of cycles)
	cbi	DDRD,SDAP		;	release SDA
	rjmp	i2c_write_high
i2c_write_low:				; else
	sbi	DDRD,SDAP		;	force SDA low
	rjmp	i2c_write_high		;	(equalize number of cycles)
i2c_write_high:
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	i2c_hp_delay		; half period delay

	rjmp	i2c_write_bit


;***************************************************************************
;*
;* FUNCTION
;*	i2c_get_ack
;*
;* DESCRIPTION
;*	Get slave acknowledge response.
;*
;* USAGE
;*	(used only by i2c_write in this version)
;*
;* RETURN
;*	Carry flag - Cleared if a slave responds to a request.
;*
;***************************************************************************

i2c_get_ack:
	sbi	DDRD,SCLP		; force SCL low
	cbi	DDRD,SDAP		; release SDA
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL

i2c_get_ack_wait:
	sbis	PIND,SCLP		; wait SCL high 
					;(In case wait states are inserted)
	rjmp	i2c_get_ack_wait

	clc				; clear carry flag
	sbic	PIND,SDAP		; if SDA is high
	sec				;	set carry flag
	rcall	i2c_hp_delay		; half period delay
	ret


;***************************************************************************
;*
;* FUNCTION
;*	i2c_do_transfer
;*
;* DESCRIPTION
;*	Executes a transfer on bus. This is only a combination of i2c_read
;*	and i2c_write for convenience.
;*
;* USAGE
;*	i2cadr - Must have the same direction as when i2c_start was called.
;*	see i2c_read and i2c_write for more information.
;*
;* RETURN
;*	(depends on type of transfer, read or write)
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_read.
;*
;***************************************************************************

i2c_do_transfer:
	sbrs	i2cadr,b_dir		; if dir = write
	rjmp	i2c_write		;	goto write data


;***************************************************************************
;*
;* FUNCTION
;*	i2c_read
;*
;* DESCRIPTION
;*	Reads data (one byte) from the I2C bus.
;*
;* USAGE
;*	Carry flag - 	If set no acknowledge is given to the slave
;*			indicating last read operation before a STOP.
;*			If cleared acknowledge is given to the slave
;*			indicating more data.
;*
;* RETURN
;*	i2cdata - Contains received data.
;*
;* NOTE
;*	IMPORTANT! : This funtion must be directly followed by i2c_put_ack.
;*
;***************************************************************************

i2c_read:
	rol	i2cstat			; store acknowledge
					; (used by i2c_put_ack)
	ldi	i2cdata,0x01		; data = 0x01
i2c_read_bit:				; do
	sbi	DDRD,SCLP		; 	force SCL low
	rcall	i2c_hp_delay		;	half period delay

	cbi	DDRD,SCLP		;	release SCL
	rcall	i2c_hp_delay		;	half period delay

	clc				;	clear carry flag
	sbic	PIND,SDAP		;	if SDA is high
	sec				;		set carry flag

	rol	i2cdata			; 	store data bit
	brcc	i2c_read_bit		; while receive register not full


;***************************************************************************
;*
;* FUNCTION
;*	i2c_put_ack
;*
;* DESCRIPTION
;*	Put acknowledge.
;*
;* USAGE
;*	(used only by i2c_read in this version)
;*
;* RETURN
;*	none
;*
;***************************************************************************

i2c_put_ack:
	sbi	DDRD,SCLP		; force SCL low

	ror	i2cstat			; get status bit
	brcc	i2c_put_ack_low		; if bit low goto assert low
	cbi	DDRD,SDAP		;	release SDA
	rjmp	i2c_put_ack_high
i2c_put_ack_low:			; else
	sbi	DDRD,SDAP		;	force SDA low
i2c_put_ack_high:

	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
i2c_put_ack_wait:
	sbis	PIND,SCLP		; wait SCL high
	rjmp	i2c_put_ack_wait
	rcall	i2c_hp_delay		; half period delay
	ret


;***************************************************************************
;*
;* FUNCTION
;*	i2c_stop
;*
;* DESCRIPTION
;*	Assert stop condition.
;*
;* USAGE
;*	No parameters.
;*
;* RETURN
;*	None.
;*
;***************************************************************************

i2c_stop:
	sbi	DDRD,SCLP		; force SCL low
	sbi	DDRD,SDAP		; force SDA low
	rcall	i2c_hp_delay		; half period delay
	cbi	DDRD,SCLP		; release SCL
	rcall	i2c_qp_delay		; quarter period delay
	cbi	DDRD,SDAP		; release SDA
	rcall	i2c_hp_delay		; half period delay
	ret


;***************************************************************************
;*
;* FUNCTION
;*	i2c_init
;*
;* DESCRIPTION
;*	Initialization of the I2C bus interface.
;*
;* USAGE
;*	Call this function once to initialize the I2C bus. No parameters
;*	are required.
;*
;* RETURN
;*	None
;*
;* NOTE
;*	PORTD and DDRD pins not used by the I2C bus interface will be
;*	set to Hi-Z (!).
;*
;* COMMENT
;*	This function can be combined with other PORTD initializations.
;*
;***************************************************************************

i2c_init:
	clr	i2cstat			; clear I2C status register (used
					; as a temporary register)
	out	PORTD,i2cstat		; set I2C pins to open colector
	out	DDRD,i2cstat
	ret


;***************************************************************************
;*
;* PROGRAM
;*	main - Test of I2C master implementation
;*
;* DESCRIPTION
;*	Initializes I2C interface and shows an example of using it.
;*
;***************************************************************************

RESET:
main:	rcall	i2c_init		; initialize I2C interface

;**** Write data => Adr(00) = 0x55 ****

	ldi	i2cadr,$A0+i2cwr	; Set device address and write
	rcall	i2c_start		; Send start condition and address

	ldi	i2cdata,$00		; Write word address (0x00)
	rcall	i2c_do_transfer		; Execute transfer

	ldi	i2cdata,$55		; Set write data to 01010101b
	rcall	i2c_do_transfer		; Execute transfer

	rcall	i2c_stop		; Send stop condition

;**** Read data => i2cdata = Adr(00) ****

	ldi	i2cadr,$A0+i2cwr	; Set device address and write
	rcall	i2c_start		; Send start condition and address

	ldi	i2cdata,$00		; Write word address
	rcall	i2c_do_transfer		; Execute transfer

	ldi	i2cadr,$A0+i2crd	; Set device address and read
	rcall	i2c_rep_start		; Send repeated start condition and address

	sec				; Set no acknowledge (read is followed by a stop condition)
	rcall	i2c_do_transfer		; Execute transfer (read)

	rcall	i2c_stop		; Send stop condition - releases bus

	rjmp	main			; Loop forewer

;**** End of File ****

